iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 2
2
Modern Web

Fabricjs 筆記系列 第 2

Day 2 - Fabricjs 和原生 Canvas 比較

  • 分享至 

  • xImage
  •  

Fabricjs 是基於 HTML5 Canvas 來撰寫的 Library,能讓我們更簡單、更方便的來操作 Canvas,在學習 Fabricjs 之前,我們就先來了解看看使用原生 Canvas api 和使用 Fabricjs 操作上有什麼差異吧。

這篇筆記可能會使用到一些 ES6 語法,有用到的相關連結我都會貼到最下面讓大家參考。
相關程式碼會放上 codepen 方便大家瀏覽和體驗 Fabricjs。

註:

  1. fabricjs 使用的版本是 2.4.1
  2. 使用 babel 編譯 es6 語法

建立簡單的圖形

讓我們先來建立一個矩形

原生 canvas

// 原生 canvas 建立矩形
nativeCtx.rect(10,10,30,30)
nativeCtx.fill()

fabricjs

// 透過 fabric.Rect 新增一個 fabric Rect Object
const rect = new fabric.Rect({
  top: 10,
  left: 10,
  width: 30,
  height: 30
})
// 加入到 canvas 中
fabricCanvas.add(rect)

我們這樣看下來, 若只單純新增一個矩形,原生 Canvas 看起來是比較輕鬆的。

而使用 Fabricjs 雖然看起來行數比較多,但因為是透過新增物件方式來建立一個矩形,標明了每個屬性的名稱和給的數值,在閱讀程式上比較直覺一些。

移動


再來移動一下我們剛剛新增的矩形

原生 Canvas

nativeCtx.clearRect(0, 0, nativeCanvas.width, nativeCanvas.height)
nativeCtx.beginPath()
nativeCtx.rect(50,50,30,30)
nativeCtx.fill()

Fabricjs

rect.set({
top: 50,
left: 50
})
canvas.renderAll()

我們看到兩者差異,原生 Canvas 直接就是重畫了一個新的矩形。

而 Fabricjs 則是使用了我們剛剛 new 出來的 rect 物件的 set 方法直接改完位置後,Fabricjs 會自動重整一次畫布。

旋轉


接下來我們做旋轉矩形

原生 Canvas

// 儲存一下我們初始狀態,因為要來移動整個畫布
nativeCtx.save()
// 清掉畫布
nativeCtx.clearRect(0, 0, nativeCanvas.width, nativeCanvas.height)
nativeCtx.beginPath()
// 改變一下中心位置
nativeCtx.translate(30 + 15, 30 + 15)
// 旋轉一下
nativeCtx.rotate(45 * Math.PI / 180)
// 再畫一次
nativeCtx.rect(-15,-15,30,30)
nativeCtx.fill()
nativeCtx.restore()

Fabricjs

// 再次使用剛剛我們 new 出來的 rect 物件
rect.rotate(45)
fabricCanvas.renderAll()

看到使用原生 Canvas 時,也是先清空畫布,再去做一些座標計算和旋轉,最後在畫上一個矩形。

**註:這邊用 save & restore 因為若是今天要操控多個矩形,我們還要把整個畫布中心移來移去。

nativeCtx.save()
nativeCtx.translate(30 + 15, 30 + 15)
nativeCtx.restore()

通常會用 save 和 restore 來記錄和往返畫布沒有更動過的狀態
這邊我們若使用 Fabricjs 可以很簡單地透過操控我們剛剛所 new 出來的物件去旋轉。

總結

原生 canvas Fabricjs
可讀性 較低且需要自己包裝 win
自由度 win 較低但也是有能控制原生的方法
便利性 win

我們能夠透過 Fabricjs 來很方便地透過修改物件的方式,來更新我們畫布上的圖形。這在修改大量圖形上十分方便,不需要自己直接接觸原生 Canvas 操作。

但別忘了 Fabricjs 也是以 canvas 為基礎,來提供的函式庫,只是它包裝了更物件化的寫法,使用起來更加直覺、簡單。

本日程式碼 - codepen


上一篇
Day 1 - 基本介紹
下一篇
Day 3 - 畫布設置
系列文
Fabricjs 筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
advancedor96
iT邦新手 5 級 ‧ 2019-03-19 15:25:42

看到表格裡的 win 還以為是 windows XDDDDDD

0
jack1234552000
iT邦新手 5 級 ‧ 2019-05-24 05:27:02

請問為什麼Fabric
要用render重新整理一次畫布
圖不是已經畫上去了嗎?

看更多先前的回應...收起先前的回應...
Nono iT邦新手 5 級 ‧ 2019-05-24 09:49:12 檢舉

Hi, 因為當我們對 Fabric 畫布做數據更新 (etc 新增物件、變更物件屬性...)時,Fabric 畫布實際上是還沒有畫上去的。

但為什麼我們新增物件時畫布就自動更新了呢?

這是因為 Fabric 的 Canvas 有一個屬性 renderOnAddRemove 為一個 Boolean 型態,預設為 true

字面上的意思也就是當新增或刪除就會 render 一次畫布。

可以試著把這個參數設定成 false,我們就可以每次都使用 renderAll 來自己 render 圖形囉!

const canvas = new fabric.Canvas('canvas', 
  {
    ...,
    renderOnAddRemove: false
  })
  canvas.add(rect)
  canvas.renderAll()
  rect.rotate(45)
  window.setTimeout(() => canvas.renderAll(), 5000)

Codepen 玩玩看
最後總結一下 Fabricjs 對物件繪製的流程

  1. 建立圖形 Object 數據
  2. fabric 讀取圖形 Object 數據
  3. canvas 繪製圖形

既然預設是renderOnAddRemove = true
新增或刪除他都會自動幫我們render一次畫布
那我們為什麼要自己再打上renderAll()呢?

Nono iT邦新手 5 級 ‧ 2019-05-29 10:05:29 檢舉

主要是因為在操作 set() 和 rotate() 這些非新增或刪除的操作時,物件不會更新!

Nono iT邦新手 5 級 ‧ 2019-05-29 10:38:47 檢舉

當 renderOnAddRemove = true 時

再看看下面這段程式不使用 canvas.renderAll() 為什麼還是可以讓 rect 旋轉。
canvas.add(rect)
rect.rotate(45)

這邊是因為 fabricjs 再處理 canvas.add() 時,其實是將 rect 的 data,丟進 objects 裡面,過一會兒才做刷新畫布。

重點其實在刷新畫布的部分,fabircjs 是使用 requestAnimationFrame 來做更新。

所以事實上畫面不會馬上就更新,而是先執行了 rect.rotate(45),rect 物件在 objects 中的 rotate 資訊已經被改成 45。

requestAnimationFrame 綁定的 function 被執行時,抓出 Objects 的物件已經是被旋轉過的 rect

這邊如果使用我們在一秒後再做 rect.rotate 就會發現 rect 旋轉沒有被更新,就需要在呼叫一次 canvas.renderAll()

canvas.add(rect)

window.setTimeout(() => rect.rotate(45), 1000)

相關參考:
fabricjs doc:
Canvas.add 原始碼

0
jack1234552000
iT邦新手 5 級 ‧ 2019-05-29 01:09:41

請問還在嗎?

Nono iT邦新手 5 級 ‧ 2019-05-29 09:57:57 檢舉

HI, 不好意思,沒有看到通知!!

我要留言

立即登入留言